/* Copyright 2001,2002,2003 NAH6 BV
 * All Rights Reserved
 *
 *  $Header: /var/lib/cvs/secphone/ui/phone/PhoneHandler.cpp,v 1.22 2003/11/21 16:12:58 itsme Exp $
 *
 *
 *
 * this module handles the phone state ( calling, receiving, ringing, etc )
 *
 * 2 functions handle the main functionality of the phone: HandleOutgoingCall,
 * and  HandleIncomingCall.
 *
 */

/*
- answer/end key should always be disabled briefly,
  to avoid 'contact dender'

- caller crashes after remote hungup during keyexchange
   * this appears to happen inside a call to tapi in the machine.

- hangup at start of keyexchange may fail.
   * this also appears to be a TAPI problem. - hangup while a UDC
     message is being transmitted somehow fails.

- when using a gsm to call a modem, the gsm-hangup does not cause the
  modem-phoneline to be hungup. - somehow the gsm network does not
  pass on the hangup properly

- todo: to work around these hangup problems, I need send a 'hangup' message
  and wait 1 second before really hanging up.

- todo: need to remove all the action code from the 'wait' functions
  and structure hangup handling better, now hangup is done in 10 slightly
  different ways.

- move the WaitState stuff to a parent class, which can then be shared with
  the DiffieHellmanHandler.

  */
#include "CPhone.h"

#include <windows.h>

// these 2 includes are to translate the various result codes from disconnect
#include "wintapi.h"
#include "spipc.h"

#include "PhoneHandler.h"

#include "UDCAutoAnswer.h"
#include "UDCPinger.h"

#include "esrcAudioInput.h"

#include "DiffieHellmanHandler.h"
#include "CPhoneDlg.h"
#include "SpipcHandler.h"
#include "VolumeManager.h"
#include "CallProgressSignalling.h"

#include "PhoneOptions.h"

#include "stringutils.h"
#include <string>

#ifdef _WIN32_WCE
#include "killapp.h"        // RestartCProgExe, KillCProgExe
#endif

// 2 types of events:
//    - indicating the presence of a state ( hangup, syncloss )
//    - indicating a single event has happend.

inline DWORD BIT(int a) { return(1<<a); }
inline DWORD BITS(int a) { return BIT(a); }
inline DWORD BITS(int a, int b) { return BIT(a)|BIT(b); }
inline DWORD BITS(int a, int b, int c) { return BIT(a)|BIT(b)|BIT(c); }
inline DWORD BITS(int a, int b, int c, int d) { return BIT(a)|BIT(b)|BIT(c)|BIT(d); }
inline DWORD BITS(int a, int b, int c, int d, int e) { return BIT(a)|BIT(b)|BIT(c)|BIT(d)|BIT(e); }
inline DWORD BITS(int a, int b, int c, int d, int e, int f) { return BIT(a)|BIT(b)|BIT(c)|BIT(d)|BIT(e)|BIT(f); }



#define MAX_WAIT_EVENTS 8
#define EVENTMASK BITS

inline void SETBITS(DWORD& f, int a) { f |= BITS(a); }
inline void SETBITS(DWORD& f, int a, int b) { f |= BITS(a,b); }
inline void SETBITS(DWORD& f, int a, int b, int c) { f |= BITS(a,b,c); }

inline bool TESTALL(DWORD f, int a) { return (f&BITS(a))==BITS(a); }
inline bool TESTALL(DWORD f, int a, int b) { return (f&BITS(a,b))==BITS(a,b); }
inline bool TESTALL(DWORD f, int a, int b, int c) { return (f&BITS(a,b,c))==BITS(a,b,c); }

static std::string eventname(int n)
{
    switch(n)
    {
    case EV_SP_RINGON:      return "EV_SP_RINGON";
    case EV_SP_RINGOFF:     return "EV_SP_RINGOFF";
    case EV_UI_USERREQUEST: return "EV_UI_USERREQUEST";
    case EV_UI_USERSTOP:    return "EV_UI_USERSTOP";
    case EV_UI_MUTE_ON:     return "EV_UI_MUTE_ON";
    case EV_UI_MUTE_OFF:    return "EV_UI_MUTE_OFF";
    case EV_SP_CONNECT:     return "EV_SP_CONNECT";
    case EV_SP_DISCONNECT:  return "EV_SP_DISCONNECT";
    case EV_SP_DIALSTART:   return "EV_SP_DIALSTART";
    case EV_SP_ANSWERSTART: return "EV_SP_ANSWERSTART";
    case EV_RCV_AUTOANSWER_WAIT: return "EV_RCV_AUTOANSWER_WAIT";
    case EV_RCV_AUTOANSWER_CONNECT:    return "EV_RCV_AUTOANSWER_CONNECT";
    case EV_KX_DONE:        return "EV_KX_DONE";
    case EV_KX_FAILED:      return "EV_KX_FAILED";
    case EV_TIMER:          return "EV_TIMER";
    default:
        return stringformat("EVENT-%d-UNKNOWN", n);
    }
}
// todo: extract the 'WaitState' stuff to a 'StateMachine' class
int PhoneHandler::WaitState(DWORD eventMask)
{
    HANDLE hWaitEvents[MAX_WAIT_EVENTS];
    int nEventIds[MAX_WAIT_EVENTS];

    m_dwCurrentWait= eventMask;

    debug("PH: waiting for");

    // convert eventmask to list of event handles
    DWORD j=0;
    for (int i=0 ; i<32 ; i++)
    {
        if (m_dwCurrentWait&(1<<i))  {
            if (j<MAX_WAIT_EVENTS)
            {
                debug(" %d=%hs", j, eventname(i).c_str());

                nEventIds[j]= i;
                hWaitEvents[j]= m_hEvents[i];

                j++;
            }
            else
                debug("PH-ERROR - too many events in wait\n");
        }
    }
    debug("\n");

#if 0
//  ---- write current state of all events
    for (i=0 ; i<j ; i++)
    {
        int rs= WaitForSingleObject(hWaitEvents[i], 0);
        debug(" %d=(%d, %s)", i, rs, eventname(nEventIds[i]).c_str());
    }
    debug("\n");
#endif
	//////////Frank///////
	 //       if(firstFlag)
		//{
		//	SignalUserRequest();
		//	SignalUserStop();
		//}
			///////////
    DWORD res= WaitForMultipleObjects(j, hWaitEvents, false, INFINITE);
    if (res>=WAIT_OBJECT_0 && res<WAIT_OBJECT_0+j)
    {
        debug("PH: event %d=%hs\n", res-WAIT_OBJECT_0, eventname(nEventIds[res-WAIT_OBJECT_0]).c_str());

        ResetEvent(hWaitEvents[res-WAIT_OBJECT_0]);

        return nEventIds[res-WAIT_OBJECT_0];
    }

    if (res>=WAIT_ABANDONED_0  && res<WAIT_ABANDONED_0 +j)
    {
        debug("PH-WaitForMultipleObjects ERROR %hs abandoned wait", eventname(nEventIds[res-WAIT_ABANDONED_0]).c_str());
        return -1;
    }
    if (res==WAIT_TIMEOUT)
        debug("PH-WaitForMultipleObjects ERROR timeout\n");
    else if (res==WAIT_FAILED)
        error("PH-WaitForMultipleObjects failed ");
    else
        error("PH-WaitForMultipleObjects error ");

    return -1;
}


PhoneHandler::PhoneHandler(CCPhoneDlg& dlg) :
    m_call_answered(false),
    m_disconnect_reason(SP_DISCONNECT_NONE),
    m_dlg(dlg)
{

    // create events, manual reset, initially nonsignalled
    for (int i=0 ; i<PHONE_NR_OF_EVENTS ; i++)
        m_hEvents[i]= CreateEvent(0, TRUE, FALSE, 0);
	/*firstFlag = true;*/
    m_dwCurrentWait= 0;
}

PhoneHandler::~PhoneHandler()
{
    for (int i=0 ; i<PHONE_NR_OF_EVENTS ; i++)
    {
        CloseHandle(m_hEvents[i]);
        m_hEvents[i]= NULL;
    }
}
void PhoneHandler::StartThread()
{
    debug("starting phonehandler thread\n");
    NHThread::StartThread();
    debug("phonehandler thread started\n");
}

DWORD PhoneHandler::ThreadProc()
{
    mainloop();

    return 0;
}

/////////////////////////////////////////////////////////////////////////

void PhoneHandler::Reset()
{
    for (int i=0 ; i<PHONE_NR_OF_EVENTS ; i++)
        ResetEvent(m_hEvents[i]);
}

/////////////////////////////////////////////////////////////////////////

void PhoneHandler::mainloop()
{
    bool exitloop= false;
    int tick= 0;

    debug("PH: starting main phone state handler thread\n");


    while (!exitloop)
    {
        m_dlg.EnableTalkButton(true);
        m_dlg.EnableHangupButton(true);
        theApp.m_dlg->SetUpDownToScrollButtons();

        theApp.m_aa->StopSending();
        theApp.m_cptone->StopKeyExchangeTone();

        m_dlg.ResetMuteButton();

        //theApp.m_lic->StopSending();
        m_call_answered= false;
        Reset();
        int n= WaitState(EVENTMASK(EV_UI_USERREQUEST, EV_SP_RINGON, EV_UI_USERSTOP, EV_TIMER));
        switch(n)
        {

        case EV_UI_USERREQUEST:
            DisableTimer();
#ifdef _WIN32_WCE
            KillCProgExe(theApp.m_dlg->m_hWnd);
#endif
            HandleOutgoingCall();
#ifdef _WIN32_WCE
            RestartCProgExe(theApp.m_dlg->m_hWnd);
#endif

            theApp.m_dlg->ClearKeySignature();

            EnableTimer();
            tick= 0;
            break;
        case EV_SP_RINGON:
            DisableTimer();
#ifdef _WIN32_WCE
            KillCProgExe(theApp.m_dlg->m_hWnd);
#endif
            HandleIncomingCall();
#ifdef _WIN32_WCE
            RestartCProgExe(theApp.m_dlg->m_hWnd);
#endif

            theApp.m_dlg->ClearKeySignature();

            EnableTimer();
            tick= 0;
            break;
        case EV_UI_USERSTOP:
            DisableTimer();

            theApp.m_cptone->Stop();
            theApp.m_cptone->StopKeyExchangeTone();

            // extra, just to be sure, work around possible machine bug
            // where hangup is not handled correctly while answering
            DoHangup();
            m_dlg.ResetMuteButton();

            break;
        case EV_TIMER:
            tick++;
            if (tick==1) {
                // after one second stop all messages
                // this is to make sure any optional 'hangup' messages do get sent.
                theApp.m_ud->StopAll();
            }
            else if (tick==4)
            {
                DisableTimer();
                theApp.m_cptone->Stop();
                theApp.m_cptone->StopKeyExchangeTone();
                theApp.m_volmgr->SignalPhoneDisconnect();
            }
            break;
        case -1:
            exitloop=true;
            break;
        default:
            debug("PH-invalid event %hs, expected request, ringon or stop\n", eventname(n).c_str());
        }


    }
}

/////////////////////////////////////////////////////////////////////////

void PhoneHandler::HandleOutgoingCall()
{
    Reset();

    m_dlg.EnableTalkButton(false);
    m_dlg.EnableHangupButton(false);

    m_dlg.SetStatus("Dialing");

    theApp.m_audioinput->StartAudioInput();
    theApp.m_dlg->SetUpDownToVolumeControl();
    theApp.m_volmgr->SignalVoiceConnect();

    if (!theApp.m_ipc->DoMakeCall())
    {
        debug("PH- error making call\n");
        m_dlg.SetStatus("Error");
        theApp.m_audioinput->StopAudioInput();
        return;
    }

    // now after about 180-800 ms we will receive a SPIPC_PRO_DIALSTART event

    if (!WaitForDialStart())
    {
        debug("PH- error waiting for dial start\n");
        theApp.m_audioinput->StopAudioInput();
        return;
    }

    Sleep(100);
    m_dlg.EnableTalkButton(false);
    m_dlg.EnableHangupButton(true);

    // this wait takes about 15 seconds
    if (!WaitForOutgoingConnect())
    {
        debug("PH- error waiting for outgoing connect\n");
        theApp.m_audioinput->StopAudioInput();
        return;
    }

    theApp.m_audioinput->StopAudioInput();
    m_dlg.SetStatus("Key exchange");

    // now we are connected

    theApp.m_dh->HandleUserStartRequest();
    theApp.m_cptone->StartKeyExchangeTone();

    if (!WaitForOutgoingKeyExchange())
    {
        debug("PH- error waiting for keyexchange\n");
        return;
    }

    m_dlg.SetStatus("Key negotiated");

    // now key is negotiated

    if (!m_call_answered && !WaitForRemoteAnswer())
    {
        debug("PH- error waiting for remote answer\n");
        return;
    }

    theApp.m_cptone->SignalConnect();       // 'poing'
    theApp.m_ipc->EnableVoiceCodec();
    // now we are having an open voice channel

    m_dlg.SetStatus("Call established");

    theApp.m_pinger->StopSending();
    theApp.m_pinger->Start(g_options.ping_interval, g_options.ping_size);

    theApp.m_aa->StopSending();

    if (!WaitForHangup())
    {
        debug("PH- error in call\n");
        return;
    }
    theApp.m_pinger->StopSending();

    debug("PH: call ended\n");
    return;
}


// this function waits for the autoanswer to connect
// after the keyexchange has finished
bool PhoneHandler::WaitForRemoteAnswer()
{
    while (true)
    {
        int n= WaitState(EVENTMASK(EV_SP_DISCONNECT, EV_UI_USERSTOP, EV_RCV_AUTOANSWER_CONNECT, EV_RCV_AUTOANSWER_WAIT));
        switch(n)
        {
        case EV_SP_DISCONNECT:  // call was rejected
            debug("PH: remote disconnect - %hs\n", GetNameForReason(m_disconnect_reason));
            theApp.m_cptone->Start(GetToneForReason(m_disconnect_reason));
            theApp.m_cptone->StopKeyExchangeTone();
            m_dlg.SetFinalStatus("Disconnected");
            m_dlg.ResetMuteButton();
            return false;
        case EV_UI_USERSTOP:
            debug("PH: local user abort\n");
            theApp.m_cptone->Stop();
            theApp.m_cptone->StopKeyExchangeTone();
            DoHangup();
            m_dlg.SetFinalStatus("Aborted");
            m_dlg.ResetMuteButton();

            return false;
        case EV_RCV_AUTOANSWER_CONNECT:
            debug("PH: AUTOANSWER_CONNECT: remote has answered\n");
            theApp.m_cptone->Stop();
            theApp.m_cptone->StopKeyExchangeTone();
            return true;
        case EV_RCV_AUTOANSWER_WAIT:
            debug("PH: AUTOANSWER_WAIT: remote has not answered yet\n");
            theApp.m_cptone->StopKeyExchangeTone();
            // continue waiting.  ( and play 'wake-tone' )
            break;
        default:
            debug("PH-invalid event %hs, expected disconnect, stop, or aa msg\n", eventname(n).c_str());
        }
    }
}

bool PhoneHandler::WaitForDialStart()
{
    // this may wait up to 1 second.
    int n= WaitState(EVENTMASK(EV_SP_DIALSTART,EV_UI_USERSTOP));
    switch(n)
    {
    case EV_SP_DIALSTART:
        debug("PH: dial start\n");
        return true;
    case EV_UI_USERSTOP:
        debug("PH: local user abort\n");
        theApp.m_cptone->Stop();
        DoHangup();
        m_dlg.SetFinalStatus("Aborted");
        m_dlg.ResetMuteButton();

        return false;
    default:
        debug("PH-invalid event %hs, expected disconnect, stop or connect\n", eventname(n).c_str());
        return false;
    }
}

// wait for our dialed connection to connect
bool PhoneHandler::WaitForOutgoingConnect()
{
    int tick= 0;
    EnableTimer();

    theApp.m_cptone->PlayComfortNoise();

    while (true) {
        // this may wait up to 15 seconds.
        int n= WaitState(EVENTMASK(EV_SP_CONNECT, EV_SP_DISCONNECT, EV_UI_USERSTOP, EV_TIMER));
        switch(n)
        {
        case EV_SP_DISCONNECT:
            DisableTimer();
            // remote has disconnected, is unreachable, or missed the call.
            debug("PH: remote disconnect - %hs\n", GetNameForReason(m_disconnect_reason));
            theApp.m_cptone->Start(GetToneForReason(m_disconnect_reason));
            m_dlg.SetFinalStatus("Disconnected");
            m_dlg.ResetMuteButton();
            return false;
        case EV_UI_USERSTOP:
            DisableTimer();
            debug("PH: local user abort\n");
            theApp.m_cptone->Stop();
            theApp.m_cptone->StopKeyExchangeTone();
            DoHangup();
            m_dlg.SetFinalStatus("Aborted");
            m_dlg.ResetMuteButton();

            return false;
        case EV_SP_CONNECT:
            DisableTimer();
            debug("PH: remote phone has answered\n");
            // continue with connect procedure
            return true;
        case EV_TIMER:
            // continue waiting for connect, just
            // handles 'progress' in call progress tones.
            tick++;

            if (tick==7)
                theApp.m_cptone->Start(CallProgressSignalling::CPTONE_DIALING);

            break;
        default:
            DisableTimer();
            debug("PH-invalid event %hs, expected disconnect, stop or connect\n", eventname(n).c_str());
            return false;
        }
    }
}
// wait for our initiated key exchange to finish
bool PhoneHandler::WaitForOutgoingKeyExchange()
{
    while (true)
    {
        int n= WaitState(EVENTMASK(EV_SP_DISCONNECT, EV_KX_DONE, EV_KX_FAILED, EV_UI_USERSTOP, EV_RCV_AUTOANSWER_CONNECT, EV_RCV_AUTOANSWER_WAIT));
        switch(n)
        {
        case EV_SP_DISCONNECT:
            debug("PH: remote disconnect - %hs\n", GetNameForReason(m_disconnect_reason));
            theApp.m_dh->HandleUserAbort();
            theApp.m_cptone->Start(GetToneForReason(m_disconnect_reason));
            m_dlg.SetFinalStatus("Disconnected");
            m_dlg.ResetMuteButton();
            return false;

        case EV_KX_DONE:
            debug("PH: keyexchange done\n");
            // ready to wait for either connect
            return true;

        case EV_KX_FAILED:
            debug("PH: keyexchange failed\n");
            // panic, somehow the keyexchange has failed
            theApp.m_cptone->Stop();
            theApp.m_cptone->StopKeyExchangeTone();
            DoHangup();
            m_dlg.SetFinalStatus("Exchange failed");
            m_dlg.ResetMuteButton();

            return false;

        case EV_UI_USERSTOP:
            debug("PH: local user abort\n");
            // the local user has decided to abort.
            theApp.m_dh->HandleUserAbort();
            theApp.m_cptone->Stop();
            theApp.m_cptone->StopKeyExchangeTone();
            DoHangup();
            m_dlg.SetFinalStatus("Aborted");
            m_dlg.ResetMuteButton();

            return false;

        case EV_RCV_AUTOANSWER_WAIT:
            // the other party has told us that they don't know if they want to pickup yet.
            // continue playing wake-tone.
            debug("PH: AUTOANSWER_WAIT: remote has not answered yet\n");
            break;
        case EV_RCV_AUTOANSWER_CONNECT:
            // the other party has told us that they want voice asap.
            debug("PH: AUTOANSWER_CONNECT: remote has answered\n");
            theApp.m_cptone->Stop();
            break;
        default:
            debug("PH-invalid event %hs, expected disconnect, kx_done, kx_failed, ui_stop, aa, or aa_connect\n", eventname(n).c_str());
        }
    }
}

/////////////////////////////////////////////////////////////////////////

void PhoneHandler::HandleIncomingCall()
{
    Reset();
    
    theApp.m_audioinput->StartAudioInput();

    m_dlg.SetForegroundWindow();
    m_dlg.EnableTalkButton(true);
    m_dlg.EnableHangupButton(true);

    m_dlg.SetStatus("Incoming call");

    theApp.m_dlg->SetUpDownToVolumeControl();
    theApp.m_volmgr->SignalRingon();
    Sleep(100); // wait for volume to be set
    theApp.m_cptone->StartRing();
    if (!g_options.auto_answer)
    {
        if (!WaitForManualAnswerUserInput())
        {
            debug("PH: error waiting for userinput\n");
            theApp.m_audioinput->StopAudioInput();
            return;
        }
        theApp.m_cptone->Start(CallProgressSignalling::CPTONE_ANSWERING);

        m_dlg.EnableTalkButton(false);
        m_dlg.EnableHangupButton(false);

    }

    // now want to answer the call

    theApp.m_ipc->Answer();

    m_dlg.SetStatus("Answering");

    if (!g_options.auto_answer)
    {
        // todo: wait for answerstart instead of sleeping
        Sleep(300);
        m_dlg.EnableTalkButton(false);
        m_dlg.EnableHangupButton(true);
    }
    // now after approximately 1 second, we get an 'SPIPC_EVT_RINGOFF' + a 'SPIPC_PRO_ANSWERSTART'

    // now waiting for the actual connect.
    // this wait may take up to 15 seconds, due to a deadlock in the tapi layer.
    if (!WaitForIncomingConnect())
    {
        debug("PH: error waiting for incoming connect\n");
        theApp.m_audioinput->StopAudioInput();
        return;
    }

    theApp.m_audioinput->StopAudioInput();

    theApp.m_cptone->Start(CallProgressSignalling::CPTONE_NONE);

    if (g_options.auto_answer && !m_call_answered)
        m_dlg.SetStatus("QuickConnect");
    else
        m_dlg.SetStatus("Key Exchange");
    
    // maybe I should do this on a 'KX_STARTED' event?
    theApp.m_cptone->StartKeyExchangeTone();

    if (m_call_answered)
        theApp.m_aa->SendAnsweredMessage();
    else
        theApp.m_aa->SendWaitingMessage();

    if (!WaitForIncomingKeyExchange())
    {
        debug("PH: error waiting for keyexchange\n");
        return;
    }

    m_dlg.SetStatus("Key negotiated");

    if (!m_call_answered && g_options.auto_answer)
    {
        if (!WaitForAutoAnswerUserInput())
        {
            debug("PH: error waiting for userinput\n");
            return;
        }
        m_dlg.EnableTalkButton(false);
        m_dlg.EnableHangupButton(false);

        Sleep(300);
        m_dlg.EnableHangupButton(true);
    }

    theApp.m_cptone->SignalConnect();       // 'poing'
    theApp.m_ipc->EnableVoiceCodec();

    m_dlg.SetStatus("Call established");

    theApp.m_pinger->StopSending();
    theApp.m_pinger->Start(g_options.ping_interval, g_options.ping_size);

    theApp.m_aa->StopSending();

    if (!WaitForHangup())
    {
        debug("PH: error during call\n");
        return;
    }
    theApp.m_pinger->StopSending();

    debug("PH: call ended\n");
    return;
}
bool PhoneHandler::WaitForManualAnswerUserInput()
{
    int n= WaitState(EVENTMASK(EV_SP_RINGOFF, EV_UI_USERREQUEST, EV_UI_USERSTOP));
    switch(n)
    {
        case EV_SP_RINGOFF:
            debug("PH: phone stopped ringing\n");
            // we missed the call
            theApp.m_cptone->StopRing();
            m_dlg.SetStatus("Missed call");
            // todo: add to calllog database
            return false;

        case EV_UI_USERREQUEST:
            debug("PH: user wants call\n");
            // we want the call
            theApp.m_cptone->StopRing();
            theApp.m_volmgr->SignalVoiceConnect();
            m_call_answered= true;
            // not connected yet, so cannot send aa-message.
            return true;

        case EV_UI_USERSTOP:
            debug("PH: user rejected call\n");
            // we rejected the call
            theApp.m_cptone->StopRing();
            DoHangup();
            m_dlg.SetFinalStatus("Call Rejected");
            m_dlg.ResetMuteButton();
            return false;

        default:
            debug("PH-invalid event %hs, expected ui_stop, ui_answer, ringoff\n", eventname(n).c_str());
            return false;
    }
}
// wait for answered call to connect
bool PhoneHandler::WaitForIncomingConnect()
{
    while (true)
    {
        // this may wait up to 15 seconds.
        int n= WaitState(EVENTMASK(EV_SP_CONNECT, EV_SP_DISCONNECT, EV_UI_USERREQUEST, EV_UI_USERSTOP));
        switch(n)
        {
        case EV_SP_DISCONNECT:
            debug("PH: remote disconnect - %hs\n", GetNameForReason(m_disconnect_reason));
            // the caller has aborted the call
            theApp.m_cptone->StopRing();
            theApp.m_cptone->Stop();
            m_dlg.SetFinalStatus("Disconnected");
            m_dlg.ResetMuteButton();
            return false;

        case EV_UI_USERSTOP:
            debug("PH: local user rejected the call\n");
            // we are rejecting the call
            theApp.m_cptone->StopRing();
            theApp.m_cptone->Stop();
            DoHangup();
            m_dlg.SetFinalStatus("Call Rejected");
            m_dlg.ResetMuteButton();

            return false;

        case EV_UI_USERREQUEST:
            debug("PH: local user wants the call\n");
            // we are answering a call while in autoanswer mode
            theApp.m_cptone->StopRing();
            theApp.m_volmgr->SignalVoiceConnect();
            theApp.m_cptone->Start(CallProgressSignalling::CPTONE_ANSWERING);
            m_dlg.SetStatus("Answering");

            m_dlg.EnableHangupButton(false);

            // maybe not very elegant to do the waiting here, but most likely
            // we are waiting up to 15-20 seconds for the phone to connect.
            // so plenty of time to wait 300ms.
            Sleep(300);  
            m_dlg.EnableHangupButton(true);

            m_call_answered= true;
            // not connected yet, so cannot send aa-message.
            // continue waiting for connect
            break;

        case EV_SP_CONNECT:
            debug("PH: connected to remote phone\n");
            if (!g_options.auto_answer) {
                theApp.m_cptone->StopRing();
                theApp.m_volmgr->SignalVoiceConnect();
            }
            // continue
            return true;
        default:
            debug("PH-invalid event %hs, expected disconnect, stop or connect\n", eventname(n).c_str());
            return false;
        }
    }
}

// wait for remotely initiated key exchange to finish
bool PhoneHandler::WaitForIncomingKeyExchange()
{
    while (true)
    {
        int n= WaitState(EVENTMASK(EV_SP_DISCONNECT, EV_KX_DONE, EV_KX_FAILED, EV_UI_USERSTOP, EV_UI_USERREQUEST));
        switch(n)
        {
        case EV_SP_DISCONNECT:
            debug("PH: remote disconnect - %hs\n", GetNameForReason(m_disconnect_reason));
            // other party has aborted the call
            theApp.m_dh->HandleUserAbort();
            theApp.m_cptone->StopRing();

            if (m_call_answered) {
                m_dlg.SetStatus("Disconnected");
            }
            else {
                m_dlg.SetStatus("Missed call");
                // todo: add to calllog database
            }
            
            return false;

        case EV_KX_DONE:
            debug("PH: keyexchange done\n");
            // ready to wait for either connect
            theApp.m_cptone->StopKeyExchangeTone();
            return true;

        case EV_KX_FAILED:
            debug("PH: keyexchange failed\n");
            // panic, somehow the keyexchange has failed
            theApp.m_cptone->StopKeyExchangeTone();
            theApp.m_cptone->StopRing();
            DoHangup();
            m_dlg.SetFinalStatus("Exchange failed");
            m_dlg.ResetMuteButton();

            return false;

        case EV_UI_USERSTOP:
            debug("PH: local users wants to abort\n");
            // the local user has decided to reject or end the call.
            theApp.m_dh->HandleUserAbort();
            theApp.m_cptone->StopKeyExchangeTone();
            theApp.m_cptone->StopRing();
            DoHangup();
            m_dlg.SetFinalStatus("Aborted");
            m_dlg.ResetMuteButton();

            return false;

        case EV_UI_USERREQUEST:
            // in autoanswer mode, 
            // the local user has decided to answer the call
            theApp.m_cptone->StopRing();
            theApp.m_volmgr->SignalVoiceConnect();

            m_dlg.EnableHangupButton(false);
            Sleep(300);
            m_dlg.EnableHangupButton(true);
            
            // change message from 'quickconnect' to 'key exchange'
            m_dlg.SetStatus("Key exchange");
            m_call_answered= true;
            theApp.m_aa->SendAnsweredMessage();
            // continue waiting for keyexchange
            break;

        default:
            debug("PH-invalid event %hs, expected disconnect, kx_done, kx_failed, ui_stop, aa, or aa_connect\n", eventname(n).c_str());
        }
    }
}

// wait for userinput after keyexchange has finished
bool PhoneHandler::WaitForAutoAnswerUserInput()
{
    int n= WaitState(EVENTMASK(EV_UI_USERREQUEST, EV_UI_USERSTOP, EV_SP_DISCONNECT));
    switch(n)
    {
        case EV_UI_USERREQUEST:
            debug("PH: we want this call\n");
            // we want the call
            theApp.m_cptone->StopRing();
            theApp.m_volmgr->SignalVoiceConnect();
            m_call_answered= true;
            theApp.m_aa->SendAnsweredMessage();
            return true;

        case EV_UI_USERSTOP:
            debug("PH: we don't want this call\n");
            // we rejected the call
            theApp.m_cptone->StopRing();
            DoHangup();
            m_dlg.SetFinalStatus("Call Rejected");
            m_dlg.ResetMuteButton();

            return false;

        case EV_SP_DISCONNECT:
            debug("PH: remote disconnect - %hs\n", GetNameForReason(m_disconnect_reason));
            // other party has aborted the call - we missed it
            theApp.m_cptone->StopRing();
            
            m_dlg.ResetMuteButton();

            if (m_call_answered)
                m_dlg.SetFinalStatus("Disconnected");
            else
            {
                m_dlg.SetFinalStatus("Missed call");
                // todo: add to calllog database
            }
            return false;

        default:
            debug("PH-invalid event %hs, expected ui_stop, ui_answer, ringoff\n", eventname(n).c_str());
            return false;
    }
}

/////////////////////////////////////////////////////////////////////////

bool PhoneHandler::WaitForHangup()
{
    while (true)
    {
        int n= WaitState(EVENTMASK(EV_SP_DISCONNECT, EV_UI_USERSTOP, EV_UI_MUTE_ON, EV_UI_MUTE_OFF));
        switch(n)
        {
        case EV_SP_DISCONNECT:
            debug("PH: remote disconnect - %hs\n", GetNameForReason(m_disconnect_reason));
            // other side has hungup.
            theApp.m_cptone->Start(GetToneForReason(m_disconnect_reason));
            m_dlg.SetFinalStatus("Disconnected");
            m_dlg.ResetMuteButton();

            return true;
        case EV_UI_USERSTOP:
            debug("PH: local users wants to abort\n");
            // we want to end the call
            DoHangup();
            m_dlg.SetFinalStatus("Disconnected");
            m_dlg.ResetMuteButton();

            return true;
        case EV_UI_MUTE_ON:
            theApp.m_ipc->MuteVoice();
            break;
        case EV_UI_MUTE_OFF:
            theApp.m_ipc->UnmuteVoice();
            break;
        default:
            debug("PH-invalid event %hs, expected ui_stop, ui_mute, disconnect\n", eventname(n).c_str());
        }
    }
}

/////////////////////////////////////////////////////////////////////////
// misc phone functions

// translating reason into something readable
PhoneHandler::TDisconnectReason PhoneHandler::FindDisconnectReason(BYTE reason, int result)
{
    // reason seems to be always set to 'SPIPC_DISCONNECT_UNSPECIFIED'

    // try to determine disconnect reason from result code instead
    switch(result)
    {
    case E_SPIPC_OK:
        return SP_DISCONNECT_LOCAL_HANGUP;

    case E_TAPI_BREAK:
        return SP_DISCONNECT_LOCAL_HANGUP;

    case E_TAPI_CALL_TIMEOUT:
        return SP_DISCONNECT_NO_ANSWER;

    case E_TAPI_CALL_BUSY:
        // this status is returned in many cases:
        //   - other phone is handling other incoming call
        //   - other phone is turned off
        //   - other phone does not have CryptoPhone running
        //   - other phone is really busy
        //   - other phone user rejected the call
        return SP_DISCONNECT_REMOTE_BUSY;

    case E_TAPI_CALL_UNREACHABLE:
        // this means the number does not exist
        return SP_DISCONNECT_UNREACHABLE;

    case E_TAPI_ANSWER2:
        return SP_DISCONNECT_REMOTE_ABORT;

    default:
        return SP_DISCONNECT_UNKNOWN;
    }
}

// note that not all tones are related to a reason,
// f.i. the 'dialing' tone is independend of a disconnect reason.
CallProgressSignalling::TCallProgressTone PhoneHandler::GetToneForReason(TDisconnectReason reason)
{
    switch(reason)
    {
    case SP_DISCONNECT_LOCAL_HANGUP:    return CallProgressSignalling::CPTONE_NONE;
    case SP_DISCONNECT_NO_ANSWER:       return CallProgressSignalling::CPTONE_NONE;
    case SP_DISCONNECT_REMOTE_ABORT:    return CallProgressSignalling::CPTONE_BUSY;
    case SP_DISCONNECT_REMOTE_BUSY:     return CallProgressSignalling::CPTONE_BUSY;
    case SP_DISCONNECT_REMOTE_HANGUP:   return CallProgressSignalling::CPTONE_BUSY;
    case SP_DISCONNECT_UNKNOWN:         return CallProgressSignalling::CPTONE_BUSY;
    case SP_DISCONNECT_UNREACHABLE:     return CallProgressSignalling::CPTONE_BUSY;
    default:                            return CallProgressSignalling::CPTONE_NONE;
    }
}
char *PhoneHandler::GetNameForReason(TDisconnectReason reason)
{
    switch(reason)
    {
    case SP_DISCONNECT_LOCAL_HANGUP:    return "SP_DISCONNECT_LOCAL_HANGUP";
    case SP_DISCONNECT_NO_ANSWER:       return "SP_DISCONNECT_NO_ANSWER";
    case SP_DISCONNECT_REMOTE_ABORT:    return "SP_DISCONNECT_REMOTE_ABORT";
    case SP_DISCONNECT_REMOTE_BUSY:     return "SP_DISCONNECT_REMOTE_BUSY";
    case SP_DISCONNECT_REMOTE_HANGUP:   return "SP_DISCONNECT_REMOTE_HANGUP";
    case SP_DISCONNECT_UNKNOWN:         return "SP_DISCONNECT_UNKNOWN";
    case SP_DISCONNECT_UNREACHABLE:     return "SP_DISCONNECT_UNREACHABLE";
    default:                            return "ERROR: invalid reason";
    }
}


/////////////////////////////////////////////////////////////////////////
//  event triggers

// event triggered by a SPIPC_PRO_DIALSTART
void PhoneHandler::SignalPhoneDialStart()
{
    debug("signalling dial start\n");
    SetEvent(m_hEvents[EV_SP_DIALSTART]);
}

// event triggered by a SPIPC_PRO_ANSWERSTART
void PhoneHandler::SignalPhoneAnswerStart()
{
    debug("signalling answer start\n");
    SetEvent(m_hEvents[EV_SP_ANSWERSTART]);
}

// event triggered by a SPIPC_EVT_RINGON
void PhoneHandler::SignalPhoneRingOn()
{
    debug("signalling ringon\n");
    SetEvent(m_hEvents[EV_SP_RINGON]);
}

// event triggered by a SPIPC_EVT_RINGOFF
void PhoneHandler::SignalPhoneRingOff()
{
    debug("signalling ringoff\n");
    SetEvent(m_hEvents[EV_SP_RINGOFF]);
}

// event triggered by a user clicking either 'call' or the
// green phone button
void PhoneHandler::SignalUserRequest()
{
    if (!PulseEvent(m_hEvents[EV_UI_USERREQUEST]))
        error("PulseEvent(USERREQUEST)");
}

// event triggered by a user clicking either 'answer' or the
// green phone button, or the headset button
void PhoneHandler::SignalUserAnswer()
{
    if (!PulseEvent(m_hEvents[EV_UI_USERREQUEST]))
        error("PulseEvent(USERREQUEST)");
}

// event triggered by a user clicking either 'end' or the
// red phone button
void PhoneHandler::SignalUserStop()
{
    if (!PulseEvent(m_hEvents[EV_UI_USERSTOP]))
        error("PulseEvent(USERSTOP)");
}

// event triggered by a user either holding down the 'action' button
// or toggling the 'mute' button to on
bool PhoneHandler::SignalUserMuteOn()
{
    SetEvent(m_hEvents[EV_UI_MUTE_ON]);

    return (m_dwCurrentWait&EVENTMASK(EV_UI_MUTE_ON))!=0;
}

// event triggered by a user either releasing the 'action' button
// or toggling the 'mute' button to off
bool PhoneHandler::SignalUserMuteOff()
{
    SetEvent(m_hEvents[EV_UI_MUTE_OFF]);

    return (m_dwCurrentWait&EVENTMASK(EV_UI_MUTE_OFF))!=0;
}

// event triggered by a SPIPC_RES_CONNECT
void PhoneHandler::SignalPhoneConnect()
{
    SetEvent(m_hEvents[EV_SP_CONNECT]);
}

// event triggered by a SPIPC_RES_DISCONNECT
void PhoneHandler::SignalPhoneDisconnect(TDisconnectReason reason)
{
    SetEvent(m_hEvents[EV_SP_DISCONNECT]);
    m_disconnect_reason= reason;
}

// event triggered by a SPIPV_EVT_HANGUP
void PhoneHandler::SignalPhoneRemoteHangup()
{
    SetEvent(m_hEvents[EV_SP_DISCONNECT]);
    m_disconnect_reason= SP_DISCONNECT_REMOTE_HANGUP;
}
// event triggered by reception of a UDC AA msg
void PhoneHandler::SignalRcvAutoAnswerWait()
{
    SetEvent(m_hEvents[EV_RCV_AUTOANSWER_WAIT]);
    m_call_answered= false;
}
// event triggered by reception of a UDC AA msg
void PhoneHandler::SignalRcvAutoAnswerConnect()
{
    SetEvent(m_hEvents[EV_RCV_AUTOANSWER_CONNECT]);
    m_call_answered= true;
}
// event triggered by succesful negotiation of a key
void PhoneHandler::SignalKeyExchangeDone()
{
    SetEvent(m_hEvents[EV_KX_DONE]);
}
// event triggered by error during negotiation of a key
void PhoneHandler::SignalKeyExchangeFailure()
{
    SetEvent(m_hEvents[EV_KX_FAILED]);
}
void PhoneHandler::EnableTimer()
{
    m_dlg.SetTimer(723, 1000, staticTimerProc);
}
void PhoneHandler::DisableTimer()
{
    m_dlg.KillTimer(723);
}
void CALLBACK PhoneHandler::staticTimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
    theApp.m_phone->TimerProc(hwnd, uMsg, idEvent, dwTime);
}
// timer to regularly interrupt waits
void PhoneHandler::TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
    PulseEvent(m_hEvents[EV_TIMER]);
}


void PhoneHandler::DoHangup()
{
    theApp.m_ipc->DisableVoiceCodec();

    // todo - test to see if sending hangup works.
    theApp.m_aa->SendHangupMessage();

    // ... test to see if we need to wait for the message to be sent?
    //     ( if this is gsm, calling modem )
    Sleep(200);
    theApp.m_ipc->Hangup();
}
